use std::{iter::Peekable, slice::Iter};

use crate::{
    data::{KeywordType, SymbolType},
    error::{CompilerError, CompilerResult},
    token::Token,
};

/// Expression compiler unit
pub(super) struct ExpressionCompilerUnit;

impl ExpressionCompilerUnit {
    /// The compiler flow unit of expression list
    pub(super) fn compile_expression_list(
        tokens: &mut Peekable<Iter<'_, Token>>,
    ) -> CompilerResult<String> {
        let mut output = vec!["<expressionList>".to_string()];
        if tokens.peek() != Some(&&Token::Symbol(SymbolType::CloseParenthesis)) {
            output.push(Self::compile_expression(tokens)?);
            while let Some(Token::Symbol(SymbolType::Comma)) = tokens.peek() {
                output.push(SymbolType::Comma.to_xml());
                let _ = tokens.next();
                output.push(Self::compile_expression(tokens)?);
            }
        }
        output.push("</expressionList>".into());
        //Ok(output.join(if output.len() == 2 { "" } else { "\n" }))
        Ok(output.join("\n"))
    }
    /// The compiler flow unit of expression
    pub(super) fn compile_expression(
        tokens: &mut Peekable<Iter<'_, Token>>,
    ) -> CompilerResult<String> {
        let mut output = vec!["<expression>".to_string(), Self::compile_term(tokens)?];

        while let Some(token) = tokens.peek() {
            if let Ok(op) = Self::flow_unit_op(token) {
                output.push(op);
                let _ = tokens.next();
                output.push(Self::compile_term(tokens)?);
            } else {
                break;
            }
        }
        output.push("</expression>".into());
        Ok(output.join("\n"))
    }
    /// The compiler flow unit of subroutine call
    ///
    /// # arguments
    ///
    /// * tokens the compiler tokens
    /// * first_identifier 因term需要判断是否varname还是subroutineCall，前序已经提取了第一个token
    pub(super) fn compile_subroutine_call(
        tokens: &mut Peekable<Iter<'_, Token>>,
        first_identifier: Option<String>,
    ) -> CompilerResult<String> {
        let first_identifier = if let Some(first_identifier) = first_identifier {
            first_identifier
        } else {
            Self::flow_unit_identifier(
                tokens
                    .next()
                    .ok_or_else(|| CompilerError::SubroutineCall("miss subroutineName".into()))?,
            )?
        };
        let mut output = vec![first_identifier];
        match tokens.next() {
            Some(Token::Symbol(SymbolType::OpenParenthesis)) => {
                // subroutineName(expressionList)
                output.push(SymbolType::OpenParenthesis.to_xml());
                output.push(Self::compile_expression_list(tokens)?);
                output.push(super::Analyzer::match_symbol(
                    tokens
                        .next()
                        .ok_or_else(|| CompilerError::SubroutineCall("miss )".into()))?,
                    SymbolType::CloseParenthesis,
                )?);
            }
            Some(Token::Symbol(SymbolType::Dot)) => {
                //className|varName.subroutineName(expressionList)
                output.push(SymbolType::Dot.to_xml());
                if let Ok(subroutine_name) =
                    Self::flow_unit_identifier(tokens.next().ok_or_else(|| {
                        CompilerError::SubroutineCall("miss subroutineName".into())
                    })?)
                {
                    output.push(subroutine_name);
                    output.push(super::Analyzer::match_symbol(
                        tokens
                            .next()
                            .ok_or_else(|| CompilerError::SubroutineCall("miss (".into()))?,
                        SymbolType::OpenParenthesis,
                    )?);

                    output.push(Self::compile_expression_list(tokens)?);

                    output.push(super::Analyzer::match_symbol(
                        tokens
                            .next()
                            .ok_or_else(|| CompilerError::SubroutineCall("miss )".into()))?,
                        SymbolType::CloseParenthesis,
                    )?);
                } else {
                    return Err(CompilerError::SubroutineCall("miss subroutine name".into()));
                }
            }
            _ => return Err(CompilerError::SubroutineCall("miss ( or .".into())),
        }

        Ok(output.join("\n"))
    }
    /// The compiler flow unit of term
    fn compile_term(tokens: &mut Peekable<Iter<'_, Token>>) -> CompilerResult<String> {
        let token = tokens
            .peek()
            .cloned()
            .ok_or_else(|| CompilerError::Term("no token".into()))?;

        let mut output = vec!["<term>".to_string()];
        if let Ok(code) = Self::flow_unit_int_const(token)
            .or(Self::flow_unit_string_const(token))
            .or(Self::flow_unit_keyword_const(token))
        {
            // skip the first peek
            let _ = tokens.next();
            output.push(code);
        } else if let Token::Symbol(SymbolType::OpenParenthesis) = token {
            // (expression)
            output.push(SymbolType::OpenParenthesis.to_xml());
            // skip (
            let _ = tokens.next();
            output.push(Self::compile_expression(tokens)?);
            output.push(super::Analyzer::match_symbol(
                tokens
                    .next()
                    .ok_or_else(|| CompilerError::Term("miss )".into()))?,
                SymbolType::CloseParenthesis,
            )?);
        } else if let Ok(unary_op) = Self::flow_unit_unary_op(token) {
            // skip unary_op
            let _ = tokens.next();
            // unaryOp term
            output.push(unary_op);
            output.push(Self::compile_term(tokens)?);
        } else if let Ok(var_name) = Self::flow_unit_identifier(token) {
            // skip varName
            let _ = tokens.next();
            // maybe varName | subroutineCall
            match tokens.peek() {
                Some(Token::Symbol(SymbolType::OpenBracket)) => {
                    // varname[expression]
                    output.push(var_name);
                    output.push(SymbolType::OpenBracket.to_xml());
                    // skip [
                    let _ = tokens.next();
                    output.push(Self::compile_expression(tokens)?);
                    output.push(super::Analyzer::match_symbol(
                        tokens
                            .next()
                            .ok_or_else(|| CompilerError::Term("miss ]".into()))?,
                        SymbolType::CloseBracket,
                    )?);
                }
                Some(Token::Symbol(symbol))
                    if [SymbolType::OpenParenthesis, SymbolType::Dot].contains(symbol) =>
                {
                    // subroutine call
                    output.push(Self::compile_subroutine_call(tokens, Some(var_name))?);
                }
                _ => output.push(var_name),
            }
        } else {
            return Err(CompilerError::Term("no a term".into()));
        }
        output.push("</term>".into());

        Ok(output.join("\n"))
    }
    /// The compiler flow unit of int const
    pub(super) fn flow_unit_int_const(token: &Token) -> CompilerResult<String> {
        if let Token::IntConst(_) = token {
            Ok(token.to_xml())
        } else {
            Err(CompilerError::IntConst(token.to_xml()))
        }
    }

    /// The compiler flow unit of int const
    fn flow_unit_string_const(token: &Token) -> CompilerResult<String> {
        if let Token::StringCost(_) = token {
            Ok(token.to_xml())
        } else {
            Err(CompilerError::StringConst(token.to_xml()))
        }
    }

    /// The compiler flow unit of identifier
    pub(super) fn flow_unit_identifier(token: &Token) -> CompilerResult<String> {
        if let Token::Identifier(_) = token {
            Ok(token.to_xml())
        } else {
            Err(CompilerError::Indentifier(token.to_xml()))
        }
    }
    /// The compiler flow unit of keyword const
    fn flow_unit_keyword_const(token: &Token) -> CompilerResult<String> {
        if let Token::Keyword(keyword) = token
            && [
                KeywordType::True,
                KeywordType::False,
                KeywordType::Null,
                KeywordType::This,
            ]
            .contains(keyword)
        {
            Ok(token.to_xml())
        } else {
            Err(CompilerError::Keyword(token.to_xml()))
        }
    }

    /// The compiler flow unit of unary opertor
    fn flow_unit_unary_op(token: &Token) -> CompilerResult<String> {
        if let Token::Symbol(symbol) = &token
            && [SymbolType::Minus, SymbolType::Not].contains(symbol)
        {
            Ok(symbol.to_xml())
        } else {
            Err(CompilerError::Operator("not a unary operator".to_string()))
        }
    }

    /// The compiler flow unit of opertor : + - * / & < > =
    fn flow_unit_op(token: &Token) -> CompilerResult<String> {
        if let Token::Symbol(symbol) = token
            && [
                SymbolType::Plus,
                SymbolType::Minus,
                SymbolType::Multiply,
                SymbolType::Divide,
                SymbolType::And,
                SymbolType::Or,
                SymbolType::LessThan,
                SymbolType::GreaterThan,
                SymbolType::Equal,
            ]
            .contains(symbol)
        {
            Ok(symbol.to_xml())
        } else {
            Err(CompilerError::Operator("not a operator".into()))
        }
    }
}
